package com.lj.common.utils.excel;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.idev.excel.ExcelWriter;
import cn.idev.excel.FastExcel;
import cn.idev.excel.metadata.AbstractParameterBuilder;
import cn.idev.excel.read.builder.ExcelReaderBuilder;
import cn.idev.excel.read.listener.ReadListener;
import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
import cn.idev.excel.write.metadata.WriteSheet;
import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.lj.common.enums.ICommonEnum;
import com.lj.common.utils.CheckUtils;
import com.lj.common.utils.EnumUtils;

import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author luojing
 * @since 2024/11/25 11:18
 * Excel工具类，需要提供excel的输入输出流，如果是web,提供web的输入流或响应流就好了(请提前设置好响应头哦！！！)
 */
public class ExcelUtil {

    private static void registerConverter(AbstractParameterBuilder<?, ?> builder, Class<?> headType) {
        if (headType == null) {
            return;
        }
        // 防止重复多次加载注册同一个枚举，进行判断
        Set<Class<?>> registerSet = new HashSet<>();
        Field[] fields = ReflectUtil.getFields(headType);
        // 如果字段是 ICommonEnum 类型的枚举，那么就进行注册转换器
        for (Field field : fields) {
            Class<?> enumClass = field.getType();
            if (registerSet.contains(enumClass) ||
                    !EnumUtils.isEnum(enumClass) ||
                    !ICommonEnum.class.isAssignableFrom(enumClass)) {
                continue;
            }
            builder.registerConverter(new CommentEnumConverter(enumClass));
            registerSet.add(enumClass);
        }
    }

    /**
     * 导出excel 用到的easy-excel 工具，可以进行多sheet导出
     *
     * @param out         导出流
     * @param excelSheets sheet 信息 参考：{@link ExcelSheet}
     */
    @Deprecated
    public static void exportExcel(OutputStream out, ExcelSheet<?>... excelSheets) {
        writeExcel(out, excelSheets);
    }

    /**
     * 导出excel 用到的easy-excel 工具，可以进行多sheet导出
     *
     * @param out         导出流 如果是web环境，请提前设置响应头
     * @param excelSheets sheet 信息 参考：{@link ExcelSheet}
     */
    public static void writeExcel(OutputStream out, ExcelSheet<?>... excelSheets) {
        CheckUtils.ifEmpty(excelSheets, "导出excel sheet不能为空！");
        try (ExcelWriter excelWriter = FastExcel.write(out)
                // 不进行自动关闭流,如果写出发生错误会返回json
                .autoCloseStream(false)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                .build()) {
            int i = 1;
            for (ExcelSheet<?> excelSheet : excelSheets) {
                ExcelWriterSheetBuilder sheetBuilder = FastExcel
                        .writerSheet(StrUtil.isBlank(excelSheet.getSheetName()) ? "Sheet" + (i++) : excelSheet.getSheetName());
                if (excelSheet.getHeadType() != null) {
                    sheetBuilder.head(excelSheet.getHeadType());
                    registerConverter(sheetBuilder, excelSheet.getHeadType());
                } else if (CollUtil.isNotEmpty(excelSheet.getHeadList())) {
                    sheetBuilder.head(excelSheet.getHeadList());
                    // 如果是业务需要自定义的表头，那么取一个导出的元素进行判断是否需要注册枚举的转换器
                    Object first = CollUtil.getFirst(excelSheet.getDataList());
                    if (first != null && !(first instanceof List)) {
                        registerConverter(sheetBuilder, first.getClass());
                    }
                }
                WriteSheet writeSheet = sheetBuilder.build();
                excelWriter.write(excelSheet.getDataList(), writeSheet);
            }
            excelWriter.finish();
        }
    }

    /**
     * 异步读取excel
     *
     * @param in       excel输入流
     * @param headType 表头类型
     * @param listener easyexcel 的监听器
     * @param <T>      数据类型
     */
    public static <T> void readExcel(InputStream in, Class<T> headType, ReadListener<T> listener) {
        readExcel(in, headType, (Integer) null, listener);
    }

    /**
     * 异步读取excel
     *
     * @param in       excel输入流
     * @param headType 表头类型
     * @param sheetNo  读第几个sheet
     * @param listener easyexcel 的监听器
     * @param <T>      数据类型
     */
    public static <T> void readExcel(InputStream in, Class<T> headType, Integer sheetNo, ReadListener<T> listener) {
        ExcelReaderBuilder read = FastExcel.read(in, headType, listener);
        registerConverter(read, headType);
        read.sheet(sheetNo).doRead();
    }

    /**
     * 异步读取excel
     *
     * @param in        excel输入流
     * @param headType  表头类型
     * @param sheetName sheet的名称
     * @param listener  easyexcel 的监听器
     * @param <T>       数据类型
     */
    public static <T> void readExcel(InputStream in, Class<T> headType, String sheetName, ReadListener<T> listener) {
        ExcelReaderBuilder read = FastExcel.read(in, headType, listener);
        registerConverter(read, headType);
        read.sheet(sheetName).doRead();
    }

    /**
     * 同步读取excel 如果excel的数据量大的话不推荐使用，因为会把excel的所有数据读取到内存中
     *
     * @param in       excel输入流
     * @param headType 表头类型
     * @param <T>      数据类型
     * @return excel数据
     */
    public static <T> List<T> readExcel(InputStream in, Class<T> headType) {
        return readExcel(in, headType, (Integer) null);
    }

    /**
     * 同步读取excel 如果excel的数据量大的话不推荐使用，因为会把excel的所有数据读取到内存中
     *
     * @param in       excel输入流
     * @param headType 表头类型
     * @param sheetNo  读第几个sheet
     * @param <T>      数据类型
     * @return excel数据
     */
    public static <T> List<T> readExcel(InputStream in, Class<T> headType, Integer sheetNo) {
        ExcelReaderBuilder read = FastExcel.read(in);
        registerConverter(read, headType);
        return read.head(headType).sheet(sheetNo).doReadSync();
    }

    /**
     * 同步读取excel 如果excel的数据量大的话不推荐使用，因为会把excel的所有数据读取到内存中
     *
     * @param in        excel输入流
     * @param headType  表头类型
     * @param sheetName sheet的名称
     * @param <T>       数据类型
     * @return excel数据
     */
    public static <T> List<T> readExcel(InputStream in, Class<T> headType, String sheetName) {
        ExcelReaderBuilder read = FastExcel.read(in);
        registerConverter(read, headType);
        return read.head(headType).sheet(sheetName).doReadSync();
    }
}
